<script setup lang="ts">
import { unwrapApiBody, type ApiResponse } from '../../utils/http/factory'
import { extractFrontMatterDesc, stripFrontMatter } from '../../utils/markdown-front-matter'
import { buildPublicCanonicalUrl } from '../../utils/public-canonical-url'
import { safeExternalHref } from '../../utils/safe-external-href'
import { formatOccurredOnDisplay, formatPublishedDateOnly, occurredOnToIsoAttr } from '../../utils/timeline-datetime'

definePageMeta({
  layout: 'public',
})

const route = useRoute()
const runtimeConfig = useRuntimeConfig()
const slug = computed(() => route.params.publicSlug as string)

type PublicPostListItem = {
  title?: string | null
  excerpt?: string | null
  slug?: string | null
  publishedAt?: Date | string | null
  coverUrl?: string | null
}

type PublicTimelineItem = {
  id?: number
  title?: string | null
  occurredOn?: Date | string | null
  linkUrl?: string | null
  bodyMarkdown?: string | null
}

type PublicRssListItem = {
  title?: string | null
  canonicalUrl?: string | null
  canonical_url?: string | null
}

type ModulePayload<T> = {
  items?: T[]
  total?: number
}

type Payload = {
  user: { publicSlug: string | null; nickname: string | null; avatar: string | null }
  bio: { markdown: string } | null
  links: { label: string; url?: string; href?: string; visibility: string; icon?: string }[]
  modules?: {
    posts?: ModulePayload<PublicPostListItem>
    timeline?: ModulePayload<PublicTimelineItem>
    reading?: ModulePayload<PublicRssListItem>
  }
  posts?: ModulePayload<PublicPostListItem>
  timeline?: ModulePayload<PublicTimelineItem>
  rssItems?: ModulePayload<PublicRssListItem>
}

type NormalizedModule<T> = {
  items: T[]
  total: number
}

function normalizeModule<T>(primary?: ModulePayload<T>, fallback?: ModulePayload<T>): NormalizedModule<T> {
  const source = primary ?? fallback
  return {
    items: Array.isArray(source?.items) ? source.items : [],
    total: typeof source?.total === 'number' ? source.total : 0,
  }
}

function rssPublicHref(it: PublicRssListItem): string | undefined {
  const u = it.canonicalUrl ?? it.canonical_url
  return safeExternalHref(u)
}

function rssPublicTitle(it: PublicRssListItem): string {
  const t = it.title
  return typeof t === 'string' && t.trim().length ? t : '未命名'
}

function postPublicTitle(it: PublicPostListItem): string {
  const t = it.title
  return typeof t === 'string' && t.trim().length ? t : '未命名文章'
}

function postPublicExcerpt(it: PublicPostListItem): string {
  const e = it.excerpt
  return typeof e === 'string' ? e : ''
}

function postPublicSlug(it: PublicPostListItem): string {
  const s = it.slug
  return typeof s === 'string' ? s : ''
}

function timelinePublicTitle(it: PublicTimelineItem): string {
  const t = it.title
  return typeof t === 'string' && t.trim().length ? t : '未命名动态'
}

function timelinePublicBody(it: PublicTimelineItem): string {
  const b = it.bodyMarkdown
  return typeof b === 'string' ? b : ''
}

function timelineItemKey(e: PublicTimelineItem, i: number): string | number {
  return e.id ?? i
}

function socialHref(link: { url?: string; href?: string }): string | undefined {
  const value = link.url ?? link.href
  return safeExternalHref(value, { allowMailto: true })
}

function rssHostname(href: string | undefined): string {
  if (!href) {
    return ''
  }
  try {
    return new URL(href).hostname
  }
  catch {
    return href
  }
}

const { data, pending, error } = await useAsyncData(
  () => `public-profile-${slug.value}`,
  async () => {
    const res = await $fetch<ApiResponse<Payload>>(`/api/public/profile/${encodeURIComponent(slug.value)}`)
    return unwrapApiBody(res)
  },
  { watch: [slug] },
)

const postsModule = computed(() => normalizeModule(data.value?.modules?.posts, data.value?.posts))
const timelineModule = computed(() => normalizeModule(data.value?.modules?.timeline, data.value?.timeline))
const readingModule = computed(() => normalizeModule(data.value?.modules?.reading, data.value?.rssItems))

const BIO_PREVIEW_MAX_CHARS = 140

const bioSummary = computed(() => {
  const md = data.value?.bio?.markdown
  if (!md?.trim()) {
    return ''
  }
  const desc = extractFrontMatterDesc(md)
  if (typeof desc === 'string' && desc.trim().length > 0) {
    return desc.trim()
  }
  return stripFrontMatter(md).trim()
})

const bioPreviewText = computed(() => {
  const plain = bioSummary.value.replace(/\s+/g, ' ').trim()
  if (!plain) {
    return ''
  }
  if (plain.length <= BIO_PREVIEW_MAX_CHARS) {
    return plain
  }
  return `${plain.slice(0, BIO_PREVIEW_MAX_CHARS).trimEnd()}...`
})

const hasBioPreview = computed(() => bioPreviewText.value.length > 0)

const socialLinks = computed(() =>
  (data.value?.links ?? [])
    .map(link => ({ ...link, safeHref: socialHref(link) }))
    .filter(link => typeof link.safeHref === 'string' && link.safeHref.length > 0),
)

const readingPreviewItems = computed(() =>
  readingModule.value.items.slice(0, 2).map(item => ({
    item,
    href: rssPublicHref(item),
  })),
)
const hasModuleContent = computed(() =>
  postsModule.value.total > 0 || timelineModule.value.total > 0 || readingModule.value.total > 0,
)

const canonicalUrl = computed(() =>
  buildPublicCanonicalUrl(runtimeConfig.public.siteUrl, route.fullPath),
)

useHead(() => ({
  link: canonicalUrl.value
    ? [{ rel: 'canonical', href: canonicalUrl.value }]
    : [],
}))

usePageTitle(() => {
  const s = slug.value
  const d = data.value
  if (!d) {
    return [`@${s}`, '主页']
  }
  const name = d.user.nickname || (d.user.publicSlug ? `@${d.user.publicSlug}` : '') || s
  return [name, '主页']
})
</script>

<template>
  <div v-if="pending && !data" class="text-muted py-10">
    <UContainer>加载中…</UContainer>
  </div>
  <UContainer v-else-if="error && !data" class="py-10">
    <UAlert color="error" title="无法加载主页" />
  </UContainer>
  <UContainer
    v-else-if="data"
    class="max-w-2xl py-10 sm:py-14"
  >
    <div class="mx-auto flex w-full max-w-xl flex-col gap-10 sm:gap-12">
      <section
        class="w-full rounded-2xl border border-default/80 bg-elevated/25 px-6 py-8 text-center shadow-sm ring-1 ring-black/5 dark:ring-white/10 sm:px-10 sm:py-9"
        aria-label="个人资料"
      >
        <div class="flex flex-col items-center gap-4">
          <div v-if="data.user.avatar" class="flex justify-center">
            <img
              :src="data.user.avatar"
              alt=""
              class="h-24 w-24 rounded-full border-2 border-default object-cover shadow-md ring-4 ring-primary/10"
            >
          </div>
          <div class="space-y-1">
            <h1 class="text-balance text-2xl font-semibold tracking-tight text-highlighted sm:text-3xl">
              {{ data.user.nickname || data.user.publicSlug || slug }}
            </h1>
            <p
              v-if="data.user.nickname && data.user.publicSlug"
              class="text-sm font-medium tabular-nums text-muted"
            >
              @{{ data.user.publicSlug }}
            </p>
          </div>
          <ul
            v-if="socialLinks.length"
            class="flex flex-wrap items-center justify-center gap-2.5"
            aria-label="社交链接"
          >
            <li v-for="(l, i) in socialLinks" :key="i">
              <a
                :href="l.safeHref"
                target="_blank"
                rel="noopener noreferrer"
                :title="l.label"
                :aria-label="`${l.label}（新窗口打开）`"
                class="flex size-10 items-center justify-center rounded-full border border-default/80 bg-default/40 text-primary transition-colors hover:border-primary/35 hover:bg-elevated focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
              >
                <UIcon
                  :name="socialLinkIconName({ ...l, url: l.safeHref || '' })"
                  class="size-5 shrink-0 opacity-90"
                />
              </a>
            </li>
          </ul>
        </div>
      </section>

      <section
        v-if="hasBioPreview"
        class="w-full"
      >
        <NuxtLink
          :to="`/@${slug}/about`"
          class="block rounded-2xl border border-default/70 bg-elevated/20 p-5 transition-colors hover:border-primary/25 hover:bg-elevated/35 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary sm:p-6"
        >
          <p class="text-sm leading-relaxed text-toned sm:text-base">
            {{ bioPreviewText }}
          </p>
          <p class="mt-3 text-xs font-medium text-primary">
            查看完整介绍
          </p>
        </NuxtLink>
      </section>

      <template v-if="hasModuleContent">
        <HubModuleCard
          title="文章"
          :total="postsModule.total"
          description="最近发布的文章摘要，进入后可查看完整内容。"
          :to="`/@${slug}/posts`"
        >
          <template #preview>
            <NuxtLink
              v-for="(p, i) in postsModule.items.slice(0, 2)"
              :key="`${postPublicSlug(p)}-${i}`"
              :to="`/@${slug}/posts/${encodeURIComponent(postPublicSlug(p))}`"
              class="block rounded-lg border border-default/80 bg-default/30 px-3 py-2 transition-colors hover:bg-elevated/40"
            >
              <div class="text-sm font-medium text-highlighted">
                {{ postPublicTitle(p) }}
              </div>
              <time
                v-if="p.publishedAt"
                class="mt-1 block text-xs tabular-nums text-muted"
                :datetime="occurredOnToIsoAttr(p.publishedAt)"
              >{{ formatPublishedDateOnly(p.publishedAt) }}</time>
              <p v-if="postPublicExcerpt(p)" class="mt-1 line-clamp-2 text-xs text-muted">
                {{ postPublicExcerpt(p) }}
              </p>
            </NuxtLink>
          </template>
        </HubModuleCard>

        <HubModuleCard
          title="时光机"
          :total="timelineModule.total"
          description="近期动态与节点，仅展示入口预览。"
          :to="`/@${slug}/timeline`"
        >
          <template #preview>
            <div
              v-for="(e, i) in timelineModule.items.slice(0, 2)"
              :key="timelineItemKey(e, i)"
              class="rounded-lg border border-default/80 bg-default/30 px-3 py-2"
            >
              <div class="text-sm font-medium text-highlighted">
                {{ timelinePublicTitle(e) }}
              </div>
              <time
                v-if="e.occurredOn"
                class="mt-1 block text-xs tabular-nums text-muted"
                :datetime="occurredOnToIsoAttr(e.occurredOn)"
              >{{ formatOccurredOnDisplay(e.occurredOn) }}</time>
              <p v-if="timelinePublicBody(e)" class="mt-1 line-clamp-2 text-xs text-muted">
                {{ timelinePublicBody(e) }}
              </p>
            </div>
          </template>
        </HubModuleCard>

        <HubModuleCard
          title="阅读"
          :total="readingModule.total"
          description="最近收藏/阅读条目，进入阅读页获取完整列表。"
          :to="`/@${slug}/reading`"
        >
          <template #preview>
            <template v-for="({ item, href }, i) in readingPreviewItems" :key="i">
              <a
                v-if="href"
                :href="href"
                target="_blank"
                rel="noopener noreferrer"
                class="block rounded-lg border border-default/80 bg-default/30 px-3 py-2 transition-colors hover:bg-elevated/40"
              >
                <div class="text-sm font-medium text-highlighted">
                  {{ rssPublicTitle(item) }}
                </div>
                <div class="mt-1 text-xs text-muted">
                  {{ rssHostname(href) }}
                </div>
              </a>
              <div
                v-else
                class="rounded-lg border border-default/80 bg-default/30 px-3 py-2"
              >
                <div class="text-sm font-medium text-highlighted">
                  {{ rssPublicTitle(item) }}
                </div>
              </div>
            </template>
          </template>
        </HubModuleCard>
      </template>
      <UEmpty
        v-else
        title="这里还没有公开内容"
        description="站主尚未发布任何公开文章或动态。"
      />
    </div>
  </UContainer>
</template>
